﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;

using Bling.Core;
using Bling.WPF;
using Bling.WPF.Util;
using Bling.Slides;
using Bling.Util;
using Bling.Shaders;
using Bling.Graphics;
namespace Bling.Example {
  using Bling.Matrices;
  using Bling.WPF3D;
  using Bling.DX;
  public class ParticlesGS : HasHost {

    public interface PSInOut : ISignature {
    }
    public interface PSOut : PSInOut, IPixelInput {
      [Semantics.TexCoord()]
      PointBl Tex { get; set; }
    }
    public interface VSIn : PSInOut {
      [Semantics.Position()]
      Point3DBl Position { get; set; }
    }
    
    
    public interface VSParticleIn : ISignature {
      [Semantics.Position()]
      Point3DBl Position { get; set; }
      [Semantics.Normal()]
      Point3DBl Velocity { get; set; }
      [Semantic("TIMER")]
      DoubleBl Timer { get; set; }
      [Semantic("TYPE")]
      IntBl Kind { get; set; }
    }
    public static class PtKind {
      public static readonly int Launcher = 0; // firework launcher - launches a shell every so many seconds.
      public static readonly int Shell = 1; // unexploded shell -files from the origin and explodes into many ember1's
      public static readonly int Ember1 = 2; // basic particl, after it's emitted from a shell, it dies
      public static readonly int Ember2 = 3; // after it's emitted, it explodes again into many Ember1's
      public static readonly int Ember3 = 4; // just a differently colored ember1
    }
    public static readonly double ShellLife = 3.0;
    public static readonly double Ember1Life = 2.5;
    public static readonly double Ember2Life = 1.5;
    public static readonly double Ember3Life = 2.0;
    public static readonly int MAX_PARTICLES = 3000;

    public interface VSParticleDrawOut : ISignature {
      [Semantics.Position()]
      Point3DBl Position { get; set; }
      [Semantics.Color()]
      RGBBl Color { get; set; }
      [Semantic("RADIUS")]
      DoubleBl Radius { get; set; }
    }
    public interface PSSceneIn : IPixelInput {
      [Semantics.TexCoord()]
      PointBl Tex { get; set; }
      [Semantics.Color()]
      RGBBl Color { get; set; }
    }
    public interface Uniforms : ISignature {

      MatrixBl<D4, D4> ProjectionViewWorld { get; set; }
      MatrixBl<D4, D4> InvView { get; set; }
      DoubleBl GlobalTime { get; set; }
      DoubleBl ElapsedTime { get; set; }
      Point3DBl FrameGravity { get; set; }
      DoubleBl SecondsPerFirework { get; set; }
      IntBl NumEmber1s { get; set; }
      DoubleBl MaxEmber2s { get; set; }
      TextureBl Diffuse { get; set; }
      Texture1DBl Random { get; set; }
      TextureBl Rendered { get; set; }
    }
    public static readonly Point3DBl[] Positions = new Point3DBl[] {
        new Point3DBl( -1, 1, 0 ),
        new Point3DBl( 1, 1, 0 ),
        new Point3DBl( -1, -1, 0 ),
        new Point3DBl( 1, -1, 0 ),
    };
    public static readonly PointBl[] TexCoords = new PointBl[] {
        new PointBl(0,1), 
        new PointBl(1,1),
        new PointBl(0,0),
        new PointBl(1,0),
    };
    public static readonly Sample.State SampleLinear = new Sample.State() {
      Filter = new Sample.Filter.MinMagMip() { Mip = Sample.Option.LINEAR },
      AddressU = Sample.AddressMode.Clamp,
      AddressV = Sample.AddressMode.Clamp,
    };
    public static readonly Sample.State SamplePoint = new Sample.State() {
      Filter = new Sample.Filter.MinMagMip() { Mip = Sample.Option.POINT },
      AddressU = Sample.AddressMode.Wrap,
      AddressV = Sample.AddressMode.Wrap,
    };
    public static readonly Blend.State AdditiveBlending = new Blend.State() {
      AlphaToCoverageEnable = false,
      Target0 = { Enable = true, WriteMask = (Blend.ColorWriteEnable) 0x0F },
      Core = {
        Src = new Blend.ComplexOption() {
          Source = Blend.Source.Src,
          ColorType = Blend.ColorType.Alpha
        },
        Dest = Blend.Option.One,
        Op = Blend.Operation.Add
      },
      Alpha = { Src = Blend.Option.Zero, Dest = Blend.Option.Zero, Op = Blend.Operation.Add },
    };
    public static readonly Blend.State NoBlending = new Blend.State() {
      AlphaToCoverageEnable = false, Target0 = { Enable = false },
    };
    public static readonly DepthStencil.State DisableDepth = new DepthStencil.State() {
      Depth = {
        Enable = false, WriteMask = DepthStencil.DepthWriteMask.Zero,
      },
    };
    public static readonly int RandomDim = 32;

    // These three lines are boiler plate to start and stop the rendering loop as this canvas is active/inactive.
    //private readonly ICanStart Host;
    //public void Start() { Host.Start(); }
    //public void Stop() { Host.Stop(); }

    // the real work begins.
    public ParticlesGS() : base(false) {
      var effect = new Effect<Uniforms>();
      var VSScenemain = new Effect<Uniforms>.VertexShader<VSParticleIn, VSParticleDrawOut>(effect, 
        (uniforms, input, output, vertex) => {
        output.Position = input.Position;
        DoubleBl[] Radius = new DoubleBl[5];
        Radius[0] = 0;
        Radius[1] = 1.0;
        for (int i = 2; i < Radius.Length; i++) Radius[i] = .5;
        RGBBl[] Colors0 = new RGBBl[] {
          new RGBBl(1.0, 0.1, 0.1),
          new RGBBl(0.1, 1.0, 1.0),
          new RGBBl(1.0, 1.0, 0.1),
          new RGBBl(1.0, 0.1, 1.0),
          Colors.Violet.Bl().ScRGB,
          //new RGBBl(1.0, 0.5, 0.5),
        };

        output.Radius = Radius.Table(input.Kind);
        output.Color = Colors0.Table(input.Kind);
      });
      var VSPassThroughmain = new Effect<Uniforms>.VertexShader<VSParticleIn, VSParticleIn>(effect, (uniforms, input, output, vertex) => {
        output.Kind = input.Kind;
        output.Position = input.Position;
        output.Timer = input.Timer;
        output.Velocity = input.Velocity;
      });
      Func<Uniforms, DoubleBl, Point3DBl> RandomDir = (uniforms, offset) => {
        //return 10000d;
        var Coord = (uniforms.GlobalTime / 10d + offset) / 300.0;
        var p = (Point3DBl)uniforms.Random.SampleLevel(SamplePoint, 0)[Coord].ScRGB;
        //p = p * 10000d;
        //p = p - ((Point3DBl)5000d);
        return p;
      };
      Func<Uniforms, VSParticleIn, Effect.GeometryStream<VSParticleIn, Topology.PointKind>, Effect.GeometryStream<VSParticleIn, Topology.PointKind>> GSGenericHandler = (uniforms, input, stream) => {
        var output = ShaderSigs.Copy(input);
        //Point3DBl useVelocity = (false) ? input.Velocity : new Point3DBl(0,10,0);
        output.Position = input.Position + input.Velocity; // input.Velocity * .001d; // *uniforms.ElapsedTime;
        output.Velocity = input.Velocity - new Point3DBl(0, .0000015, 0);
        output.Timer = input.Timer - uniforms.ElapsedTime;
        return stream.Append(output);
      };
      Func<Uniforms, VSParticleIn, Effect.GeometryStream<VSParticleIn, Topology.PointKind>, Effect.GeometryStream<VSParticleIn, Topology.PointKind>> GSLauncherHandler = (uniforms, input, stream) => {
        var generateNew = input.Timer <= 0;
        var random = RandomDir(uniforms, input.Kind).Normalize;
        DoubleBl SecondsPerFirework = false ? uniforms.SecondsPerFirework + random.X * 0.4 : 2500d;
        DoubleBl UseShellLife = false ? ShellLife + random.Y * 0.5 : 5000d + 10000d * random.Y;
        {
          var output = ShaderSigs.Copy(input);
          
          output.Timer = generateNew.Condition(SecondsPerFirework, 
                                               input.Timer - uniforms.ElapsedTime);
          stream = stream.Append(output);
        }
        {
          var output = ShaderSigs.Copy(input);
          output.Position = input.Position; // +input.Velocity * uniforms.ElapsedTime;

          var velocityBase = new Point3DBl(-0.001d, 0.001d, 0d);
          velocityBase = velocityBase + new Point3DBl(random.X * 0.002d, random.Y * 0.002d, 0d);

          output.Velocity = velocityBase; 
          output.Timer = UseShellLife;
          output.Kind = PtKind.Shell;
          stream = stream.Append(generateNew, output);
        }
        return stream;
      };
      Func<Uniforms, VSParticleIn, Effect.GeometryStream<VSParticleIn, Topology.PointKind>, Effect.GeometryStream<VSParticleIn, Topology.PointKind>> GSShellHandler = (uniforms, input, stream) => {
        //return (input.Timer <= 0).Condition(stream, GSGenericHandler(uniforms, input, stream));

        Point3DBl random;
        var streamB = stream.For(5 /*uniforms.NumEmber1s */, /*100,*/ (i, streamA) => {
          random = RandomDir(uniforms, input.Kind + i).Normalize;
          var output = ShaderSigs.Copy(input);
          output.Position = input.Position + input.Velocity; // *uniforms.ElapsedTime;
          output.Velocity = input.Velocity + random * 0.001;
          output.Timer = 10000d; //  Ember1Life;
          output.Kind = PtKind.Ember1;
          return streamA.Append(output);
        });
        random = RandomDir(uniforms, input.Kind + uniforms.NumEmber1s - 1).Normalize;

        streamB = streamB.For((IntBl)(random.X.Abs * 5d /* uniforms.MaxEmber2s */), /*28,*/ (i, streamA) => {
          random = RandomDir(uniforms, input.Kind + 100 + i).Normalize;
          var output = ShaderSigs.Copy(input);
          output.Position = input.Position + input.Velocity; // *uniforms.ElapsedTime;
          output.Velocity = input.Velocity + random * 0.002;
          output.Timer = 2000d + 4000d * random.X;  // Ember2Life + 0.4 * random.X;
          output.Kind = PtKind.Ember2;
          return streamA.Append(output);
        });
        return (input.Timer <= 0).Condition(streamB, GSGenericHandler(uniforms, input, stream));
      };

      Func<Uniforms, VSParticleIn, Effect.GeometryStream<VSParticleIn, Topology.PointKind>, Effect.GeometryStream<VSParticleIn, Topology.PointKind>> GSEmber1Handler = (uniforms, input, stream) => {
        return (input.Timer > 0).Condition(GSGenericHandler(uniforms, input, stream), stream);
      };
      Func<Uniforms, VSParticleIn, Effect.GeometryStream<VSParticleIn, Topology.PointKind>, Effect.GeometryStream<VSParticleIn, Topology.PointKind>> GSEmber2Handler = (uniforms, input, stream) => {
        var streamB = stream;

        streamB = streamB.For(2, (i, streamA) => {
          var output = ShaderSigs.Copy(input);
          output.Position = input.Position + input.Velocity; // *uniforms.ElapsedTime;
          output.Velocity = input.Velocity * -1d + RandomDir(uniforms, input.Kind + i).Normalize * .001;
          output.Timer = 5000d; // Ember3Life;
          output.Kind = PtKind.Ember3;
          return streamA.Append(output);
        });
        return (input.Timer <= 0).Condition(streamB, GSGenericHandler(uniforms, input, stream));
      };
      var GSAdvancedParticlesMain = new Effect<Uniforms>.GeometryShader<VSParticleIn, VSParticleIn, Topology.PointKind, Topology.PointKind>(
        effect, (uniforms, inputs, geom) => {
          var stream = new Effect.GeometryStream<VSParticleIn, Topology.PointKind>();
          // DEBUG
          var streams = new Effect.GeometryStream<VSParticleIn, Topology.PointKind>[5];
          streams[PtKind.Launcher] = GSLauncherHandler(uniforms, inputs(0), stream);
          streams[PtKind.Shell] = GSShellHandler(uniforms, inputs(0), stream);
          streams[PtKind.Ember1] = GSEmber1Handler(uniforms, inputs(0), stream);
          streams[PtKind.Ember2] = GSEmber2Handler(uniforms, inputs(0), stream);
          streams[PtKind.Ember3] = streams[PtKind.Ember1];
          //if (true) return GSLauncherHandler(uniforms, inputs(0), stream);
          return streams.Table(inputs(0).Kind);
        });

      var GSScenemain = new Effect<Uniforms>.GeometryShader<VSParticleDrawOut, PSSceneIn, Topology.PointKind, Topology.TriangleKind>(
        effect, (uniforms, inputs, geom) => {
          var stream = new Effect.GeometryStream<PSSceneIn, Topology.TriangleKind>();
          stream = stream.For(4, (i, streamA) => {
            var output = ShaderSigs.New<PSSceneIn>();
            var position = Positions.Table(i) * inputs(0).Radius / 8d;
            output.Position = ((Point3DBl)(uniforms.ProjectionViewWorld *position)) + inputs(0).Position - new Point3DBl(0, 1d, 0);
            output.Color = inputs(0).Color;
            output.Tex = TexCoords.Table(i);
            return streamA.Append(output);
          });
          stream = stream.RestartStrip();
          return stream;
        });

      var PSceneMain = new Effect<Uniforms>.PixelShader<PSSceneIn>(effect, (uniforms, input, pixel) => {
        pixel.Color = uniforms.Diffuse.Sample(SampleLinear)[input.Tex] * input.Color;
      });

      var RenderParticles = new Effect<Uniforms>.Technique<VSParticleIn, PSSceneIn, Topology.PointKind, Topology.TriangleKind>(effect);
      new Effect<Uniforms>.Pass<VSParticleIn, VSParticleDrawOut, PSSceneIn, Topology.PointKind, Topology.TriangleKind>(
        RenderParticles, VSScenemain, GSScenemain, PSceneMain) {
          Blend = { State = AdditiveBlending, Factor = 0d, SampleMask = 0xFFFFFFFF },
          DepthStencil = { State = DisableDepth, StencilRef = 0 },
        };

      var AdvanceParticles = new Effect<Uniforms>.Technique<VSParticleIn, Topology.PointKind>(effect);
      new Effect<Uniforms>.PassNoPS<VSParticleIn, VSParticleIn, VSParticleIn, Topology.PointKind, Topology.PointKind>(
        AdvanceParticles, VSPassThroughmain, GSAdvancedParticlesMain) {
          DepthStencil = { State = DisableDepth, StencilRef = 0 },
        };

      TextureBl ParticleTexture = new ImageTexture<D2>("Resources/particle.png".MakePackUri(typeof(ParticlesGS)));
      Texture1DBl RandomTexture = new UserTexture<D1>(1024) {
        Init = (p) => {
          return new ColorBl((RGBBl) new Random(), new Random());
        },
      };
      PerspectiveCameraCl Camera = new PerspectiveCameraCl() {
        Position = new Point3DBl(0, 0, -170),
        Direction = { Look = new Point3DBl(0, 70, 0) },
        //FieldOfView = .25.PI().AsDegrees,
        //D3DXVECTOR3 vecEye( 0.0f, 0.0f, -170.0f );
        //D3DXVECTOR3 vecAt ( 0.0f, 70.0f, 0.0f );
        //g_Camera.SetViewParams( &vecEye, &vecAt );
        //g_Camera.SetRadius( 170.0f, 170.0f, 170.0f );
      };
      BaseBufferBl<VSParticleIn, Topology.PointKind> ParticleStart = new UserBuffer<VSParticleIn, Topology.PointKind, Topology.ListOrg>() {
        VertexCount = 1,
        F0 = (vid, input) => {
          input.Position = 0d;
          input.Velocity = new Point3DBl(0, 40, 0);
          input.Timer = -1d;
          input.Kind = PtKind.Launcher;
        },
      };
      var ParticleA = new StreamOutBuffer<VSParticleIn, Topology.PointKind>() {
        MaxCount = MAX_PARTICLES,
      };
      var ParticleB = new StreamOutBuffer<VSParticleIn, Topology.PointKind>() {
        MaxCount = MAX_PARTICLES,
      };
      bool IsFirst0 = true;
      bool SwapFromTo0 = true;

      double LastTime = 0d; // in milli
      double TimeStep = 10d;

      BoolBl IsFirst = true; IsFirst = IsFirst.StatefulMap<bool, BoolBl>(b => IsFirst0);
      BoolBl SwapFromTo = true; SwapFromTo = SwapFromTo.StatefulMap<bool, BoolBl>(b => SwapFromTo0);
      var ParticleDrawFromA = SwapFromTo.Condition((BaseBufferBl<VSParticleIn, Topology.PointKind>)ParticleA, ParticleB);
      var ParticleDrawFromB = SwapFromTo.Condition((BaseBufferBl<VSParticleIn, Topology.PointKind>)ParticleB, ParticleA);
      var ParticleStreamTo = SwapFromTo.Condition((StreamOutBufferBl<VSParticleIn, Topology.PointKind>)ParticleB, ParticleA);
      var DrawFromNowA = (IsFirst).Condition(ParticleStart, ParticleDrawFromA);
      var DrawFromNowB = ParticleDrawFromA;

      int Width = 500;
      int Height = 500;
      var RenderTarget0 = new RenderTargetTexture<D2>(Width, Height) {
        Format = new TextureFormat.Typical() {
          Width = TextureFormat.ChannelWidth.WORD,
          Type = TextureFormat.ChannelType.FLOAT,
        },
      };
      var RenderTarget1 = new RenderTargetTexture<D2>(Width, Height) {
        Format = new TextureFormat.Typical() {
          Width = TextureFormat.ChannelWidth.WORD,
          Type = TextureFormat.ChannelType.FLOAT,
        },
      };
      var RenderTargetA = SwapFromTo.Condition((RenderTargetBl)RenderTarget0.AsRenderTarget, RenderTarget1.AsRenderTarget);
      var RenderTargetB = SwapFromTo.Condition((RenderTargetBl)RenderTarget1.AsRenderTarget, RenderTarget0.AsRenderTarget);

      var RenderTargetAX = SwapFromTo.Condition((TextureBl)RenderTarget0, RenderTarget1);
      var RenderTargetBX = SwapFromTo.Condition((TextureBl)RenderTarget1, RenderTarget0);

      var RenderToScreen = effect.Make<VSIn, PSOut>((uniforms, input, output, vertex) => {
        var vid = vertex.VertexId;
        output.Position = input.Position; 
        var p = (input.Position.XY() + new PointBl(1, 1)) / 2d;
        output.Tex = new PointBl(p.X, 1d - p.Y);
      }, (uniforms, input, pixel) => {
        pixel.Color = uniforms.Rendered[input.Tex];
      }, (pass) => {
        pass.Blend = new Blend.Set() { State = AdditiveBlending, Factor = 0d, SampleMask = 0xFFFFFFFF };
        pass.DepthStencil = new DepthStencil.Set() { State = DisableDepth, StencilRef = 0 };
      });

      var DoDiffuse = effect.Make<VSIn, PSOut>((uniforms, input, output, vertex) => {
        var vid = vertex.VertexId;
        output.Position = input.Position;
        var p = (input.Position.XY() + new PointBl(1, 1)) / 2d;
        output.Tex = new PointBl(p.X, 1d - p.Y);
      }, (uniforms, input, pixel) => {
        var clr = ShaderLib.Sample(uniforms.Rendered, input.Tex, .0035, 10);
        clr = ShaderLib.Transfer(uniforms.Rendered[input.Tex].ScRGB, clr);
        clr = clr.Saturate;
        clr = uniforms.Rendered[input.Tex].ScRGB * .99d; // true ? Colors.Black.Bl().ScRGB : new RGBBl(clr.ScR, 0d, 0d);
        pixel.Color = new ColorBl(0d, 0d, clr.ScB, 1d);

      }, (pass) => {
        pass.DepthStencil = new DepthStencil.Set() { State = DisableDepth, StencilRef = 0 };
        pass.Blend = new Blend.Set() { State = NoBlending, Factor = 0d, SampleMask = 0xFFFFFFFF };
      });

      BaseBufferBl<VSIn, Topology.TriangleKind> RenderToScreenVertices = new UserBuffer<VSIn, Topology.TriangleKind, Topology.ListOrg>() {
        VertexCount = 4,
        F0 = (vid, input) => {
          Point3DBl[] points = new Point3DBl[] {
            new Point3DBl( -1.0f, -1.0f, +1.0f ), // SW
            new Point3DBl( +1.0f, -1.0f, +1.0f ), // SE
            new Point3DBl( +1.0f, +1.0f, +1.0f ), // NE
            new Point3DBl( -1.0f, +1.0f, +1.0f ), // NW
          };
          input.Position = points.Table(vid);
        },
        InitIndices = (buf) => {
          buf.AddTo(1, 0, 2);
          buf.AddTo(2, 0, 3);
        },
      };
      var UniformsManager = new DefaultSignatureManager2();
      {
        double Div = 1d; // 110d; // 1000d;
        DoubleBl CurrentTime = 1d.Bl().StatefulMap<double, DoubleBl>(d => {
          return (LastTime + TimeStep) / Div;
        });
        DoubleBl ElapsedTime = 1d.Bl().StatefulMap<double, DoubleBl>(d => {
          return TimeStep / Div;
        });
        var Uniforms = ShaderSigs.Generate<Uniforms>(UniformsManager);
        var UseMatrix = MatrixBl<D4, D4>.Identity;
        Uniforms.ProjectionViewWorld = UseMatrix; // Camera.Matrix;
        Uniforms.InvView = UseMatrix.Inverse(); // Camera.View.Invert();
        Uniforms.GlobalTime = CurrentTime;
        Uniforms.ElapsedTime = ElapsedTime;
        Uniforms.Random = RandomTexture;
        Uniforms.Diffuse = ParticleTexture;
        Point3DBl vGravity = new Point3DBl(0, +9.8f, 0);
        Uniforms.FrameGravity = vGravity * ElapsedTime;
        Uniforms.SecondsPerFirework = 0.4;
        Uniforms.NumEmber1s = 10;
        Uniforms.MaxEmber2s = 15d;
        var AutumnUri = "Resources/Autumn.jpg".MakePackUri(typeof(ParticlesGS));
        TextureBl Autumn = new ImageTexture<D2>(AutumnUri);

        Uniforms.Rendered = RenderTargetAX;
      }

      //var Host0 = new WPFHostBl(this, new PointBl(Width, Height), Device => 
      {
        var RenderA = new RenderEntry<Uniforms, VSParticleIn, VSParticleIn, Topology.PointKind, Topology.PointKind, Topology.ListOrg>() {
          StreamOut = ParticleStreamTo,
          TechniqueAlt = AdvanceParticles,
          Buffer = DrawFromNowA,
          UniformsManager = UniformsManager,
          Target = Device.Display,
        };
        var RenderB = new RenderEntry<Uniforms, VSParticleIn>() {
          Technique = RenderParticles,
          Target = RenderTargetA,
          Buffer = DrawFromNowB,
          UniformsManager = UniformsManager,
        };
        var RenderC = new RenderEntry<Uniforms, VSIn>() {
          Technique = RenderToScreen,
          Target = Device.Display,
          Buffer = RenderToScreenVertices,
          UniformsManager = UniformsManager,
        };
        var RenderD = new RenderEntry<Uniforms, VSIn>() {
          Technique = DoDiffuse,
          Target = RenderTargetB,
          Buffer = RenderToScreenVertices,
          UniformsManager = UniformsManager,
          Post = () => {
            IsFirst0 = false;
            SwapFromTo0 = !SwapFromTo0;
            LastTime += TimeStep;
          },
        };
        Device.AddRender(RenderA, RenderB, RenderC, RenderD);
      };

      Host.LeftTop = 0d;
      Host.Size = this.Bl().Size;
      Host.ZIndex = 0;

      // so start and stop work.
      //Host = Host0.CurrentValue;


    }
  }
}
